/*
 *   Copyright 2008 Luca Bellonda <lbellonda@gmail.com>
 *
 *   This program is free software; you can redistribute it and/or modify
 *   it under the terms of the GNU Library General Public License as
 *   published by the Free Software Foundation; either version 2 or
 *   (at your option) any later version.
 *
 *   This program is distributed in the hope that it will be useful,
 *   but WITHOUT ANY WARRANTY; without even the implied warranty of
 *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *   GNU General Public License for more details
 *
 *   You should have received a copy of the GNU Library General Public
 *   License along with this program; if not, write to the
 *   Free Software Foundation, Inc.,
 *   51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
 */
#include <QtDBus/QtDBus>
#include <QDBusConnection>

#include "scanthread.h"

#ifdef DUMP
#define   D(x)    x
#else
#define   D(x)
#endif

ScanThread::ScanThread(QObject *parent) : QThread(parent)
{
    m_isAbort = false;
}

ScanThread::~ScanThread()
{
    m_mutex.lock();
    m_isAbort = true;
    m_mutex.unlock();
    wait();
}


void ScanThread::run()
{
    D(printf("start thread\n"));
    QStringList resultSession;
    QStringList resultSystem;
    fai(resultSession, resultSystem);
    m_mutex.lock();
    m_resultSession = resultSession;
    m_resultSystem = resultSystem;
    m_mutex.unlock();
    D(printf("emitting \n"));
    emit scanEnd();
    D(printf("fine thread\n"));
}


void ScanThread::copyStringList(const QStringList &inp, QStringList &outp)
{
    outp.clear();
    QStringListIterator dataIterator(inp);
    while (dataIterator.hasNext()) {
        QString datum =  dataIterator.next();
        outp.append(datum);
    }
}

void ScanThread::getData(QStringList &system, QStringList &session)
{
    m_mutex.lock();
    copyStringList(m_resultSession, session);
    copyStringList(m_resultSystem, system);
    m_mutex.unlock();
}


void ScanThread::addToResult(QStringList &result, const int level, const QString &type, const QString &value)
{
    D(printf(" ADDING %s\n", QString("%1|%2|%3").arg(level).arg(type).arg(value).toAscii().data()));
    result.append(QString("%1|%2|%3").arg(level).arg(type).arg(value));
}

QString ScanThread::scanForArguments(const QString &name, QDomNode &node)
{
    QString method = name;
    bool isFirst = true ;
    D(printf("( "));
    method.append("( ");
    QDomElement element = node.firstChildElement();
    while (!element.isNull()) {
        if (element.tagName() == QLatin1String("arg")) {
            QString direction = element.attribute("direction");
            QString type = element.attribute("type");
            D(printf(" %s%s[%s]",
                     (isFirst ? "" : ","),
                     type.toAscii().data(),
                     direction.toAscii().data()));
            method.append(QString(" %1%2[%3]").arg(isFirst ? "" : ",").arg(type).arg(direction)) ;
            isFirst = false;
        }
        element = element.nextSiblingElement();
    }//for
    D(printf(")\n"));
    method.append(")");
    return method ;
}//scanForArguments()

void ScanThread::scanForMethods(QDomNode &node, const int level, QStringList &result)
{
    QDomElement element = node.firstChildElement();
    while (!element.isNull()) {
        QString name = element.attribute(QLatin1String("name"));
        if (element.tagName() == QLatin1String("method")) {
            D(printf("  METODO: %s", name.toAscii().data()));
            addToResult(result, level, "METHOD", scanForArguments(name, element));
        } else if (element.tagName() == QLatin1String("signal")) {
            D(printf("  SEGNALE: %s\n", name.toAscii().data()));
            addToResult(result, level, "SIGNAL", name);
        } else if (element.tagName() == QLatin1String("property")) {
            D(printf("  PROPRIETA: %s\n", name.toAscii().data()));
            addToResult(result, level, "PROPERTY", name);
        } else {
            D(printf("  UNKNOWN***: %s\n", name.toAscii().data()));
            addToResult(result, level, "UNKNOWN", name);
        }
        element = element.nextSiblingElement();
    }
}

void ScanThread::scan(const QString &service, QDomNode &doc, QDBusConnection &connection, const int level, QStringList &result)
{
    int nodi = doc.childNodes().count();
    for (int i = 0 ; i < nodi ; i ++) {
        QDomNode childNode = doc.childNodes().item(i) ;
        if (childNode.isElement()) {
            //printf("trovato testo %s\n", childNode.nodeValue().toAscii().data());
            QDomElement element = childNode.toElement();
            QString name = element.attribute(QLatin1String("name"));
            if (element.tagName() == QLatin1String("node")) {
                D(printf("PATH: %s\n", name.toAscii().data()));
                addToResult(result, level, "PATH", name);
                introspect(service, "/" + name, connection, level + 1, result);
                scanForMethods(element, level + 1, result);
            } else if (element.tagName() == QLatin1String("interface")) {
                D(printf("INTERFACE: %s\n", name.toAscii().data()));
                addToResult(result, level, "INTERFACE", name);
                scanForMethods(element, level + 1, result);
            }
        } //if
    }//for
    return ;
}//scan()

void ScanThread::introspect(const QString &service, const QString &path, QDBusConnection &connection, const int level, QStringList &result)
{
    QDomDocument doc;
    D(printf("...........................introspecting %s for %s\n", service.toAscii().data(), path.toAscii().data()));
    QDBusInterface iface(service, path, "org.freedesktop.DBus.Introspectable", connection);
    if (!iface.isValid()) {
        printf("..............................no valid \n");
        return ;
    }

    QDBusReply<QString> xml = iface.call("Introspect");
    D(printf("...........................replied\n"));
    if (!xml.isValid()) {
        D(printf("...........................no valid 2\n"));
        return ;
    }
    doc.setContent(xml);
    QDomElement node = doc.documentElement();
    scan(service, node, connection, level, result);
}

void ScanThread::setDBusData(const QString& name, const QString& key, const QStringList &services, QDBusConnection &connection, QStringList &result)
{
    QStringListIterator serviceIterator(services);
    while (serviceIterator.hasNext()) {
        QString service =  serviceIterator.next();
#ifdef   _SPEED_DEBUG
        if (service.startsWith("org.kde.kded")) {
#endif
            addToResult(result, 0, "SERVICE", service);
            D(printf("****  %s\n", service.toAscii().data()));
            bool   isAbort = false ;
            introspect(service, "/", connection, 1, result);
            m_mutex.lock();
            isAbort = m_isAbort;
            m_mutex.unlock();
            if (isAbort)
                break;
#ifdef   _SPEED_DEBUG
        }
#endif
    }
    D(printf("finito servizio\n"));
}

void ScanThread::fai(QStringList &resultSession, QStringList &resultSystem)
{
    QDBusConnection m_connectionSystem = QDBusConnection::systemBus();
    const QStringList systemServiceNames = m_connectionSystem.interface()->registeredServiceNames();
    setDBusData(SOURCE_SYSTEM_SERVICES, "services", systemServiceNames, m_connectionSystem, resultSystem);
    D(printf("finito sistema\n"));
    QDBusConnection m_connectionSession = QDBusConnection::sessionBus();
    const QStringList sessionServiceNames = m_connectionSession.interface()->registeredServiceNames();
    setDBusData(SOURCE_SESSION_SERVICES, "services", sessionServiceNames, m_connectionSession, resultSession);
    D(printf("finito sessione\n"));
}

void ScanThread::scan()
{
    if (!isRunning()) {
        start(LowPriority);
    }
}
